package server

import (
  "database/sql"
  "fmt"
  "net"
  "net/http"
  "path"
  "strings"

  _ "github.com/go-sql-driver/mysql"
)

type App struct {
  cfg *Config

  listener     net.Listener
  httpListener net.Listener

  closed bool

  quit chan struct{}

  access *accessLog

  db *sql.DB
}

func netType(s string) string {
  if strings.Contains(s, "/") {
    return "unix"
  } else {
    return "tcp"
  }
}

func NewApp(cfg *Config) (*App, error) {
  if len(cfg.DataDir) == 0 {
    return nil, fmt.Errorf("must set data_dir first")
  }

  app := new(App)

  app.quit = make(chan struct{})

  app.closed = false

  app.cfg = cfg

  var err error

  err = app.initListener()
  if err != nil {
    return nil, err
  }

  err = app.initAccessLog()
  if err != nil {
    return nil, err
  }

  err = app.initMysql()
  if err != nil {
    return nil, err
  }
  err = app.LoadAllFamily()
  if err != nil {
    return nil, err
  }

  return app, nil
}

func (app *App) initListener() (err error) {
  app.listener, err = net.Listen(netType(app.cfg.Addr), app.cfg.Addr)
  if err != nil {
    return
  }
  if app.cfg.HttpAddr == "" {
    return
  }
  app.httpListener, err = net.Listen(netType(app.cfg.HttpAddr), app.cfg.HttpAddr)
  return
}

func (app *App) initAccessLog() (err error) {
  if app.cfg.AccessLog == "" {
    return
  }

  if path.Dir(app.cfg.AccessLog) == "." {
    app.access, err = newAcessLog(path.Join(app.cfg.DataDir, app.cfg.AccessLog))
  } else {
    app.access, err = newAcessLog(app.cfg.AccessLog)
  }
  return
}

func (app *App) initMysql() (err error) {
  dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&timeout=5s",
    app.cfg.MysqlConf.User,
    app.cfg.MysqlConf.Passwd,
    app.cfg.MysqlConf.Host,
    app.cfg.MysqlConf.DbName)

  app.db, err = sql.Open("mysql", dsn)
  if err != nil {
    return
  }
  // check mysql connection
  err = app.db.Ping()
  if err != nil {
    return
  }
  app.db.SetMaxOpenConns(app.cfg.MysqlConf.DbMaxConn)
  return
}

func (app *App) Close() {
  if app.closed {
    return
  }

  app.closed = true

  close(app.quit)

  app.listener.Close()

  if app.httpListener != nil {
    app.httpListener.Close()
  }

  if app.access != nil {
    app.access.Close()
  }

  if app.db != nil {
    app.db.Close()
  }
}

func (app *App) Run() {
  go app.httpServe()

  for !app.closed {
    conn, err := app.listener.Accept()
    if err != nil {
      continue
    }

    newClientRESP(conn, app)
  }
}

func (app *App) httpServe() {
  if app.httpListener == nil {
    return
  }

  mux := http.NewServeMux()

  mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    newClientHTTP(app, w, r)
  })

  svr := http.Server{Handler: mux}
  svr.Serve(app.httpListener)
}
